/*
 * OpenFMS Input driver for a standard keyboard.
 *
 * DLL file has four public functions: create, destroy, get_number_of_channels and captue.
 *   create - creates an instance representing a keyboard
 *   destroy - frees resources made ny the create function
 *   get_number_of_channels - retruns the number of channels used.
 *   catpure - reads ste state of the controller and retruns the captured data.
 *
 * Copyright (C) 2010 Johan Waldeback
 *  
 * This file is part of the OpenFMS project (http://www.openfms-project.org)
 * 
 * OpenFMS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * \file KeyboardInput_dll.cpp
 * \brief Keyboard input driver (Windows).
 *
 * OpenFMS Input driver for a standard keyboard. 
 * It simulates four channels.
 * The Keys for each channel are: 
 *   \li Channel 0 - Arrow right - Arrow left.
 *   \li Channel 1 - Arrow up - arrow down.
 *   \li Channel 2 - W / S
 *   \li Channel 3 - D / A
 */

// Get rid of warning message
// (Make invisible to doxygen)
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#define DIRECTINPUT_VERSION 0x0800
#endif // DOXYGEN_SHOULD_SKIP_THIS

#include <dinput.h>
#include <windows.h>
#include <tchar.h>
#include <stdint.h>
#include <cmath>
#include <cassert>

extern "C" {

/**
 * \class KeyboardInput_HandleData
 * \brief Internal keyboard input driver handle structure.
 *
 * Contains all data the keyboard input driver needs to handle a device instance
 */

struct KeyboardInput_HandleData {
	LPDIRECTINPUT8 di;             /**< Pointer to an DirectInput8 instance */
	LPDIRECTINPUTDEVICE8 device;   /**< Pointer to a device instance */
	HWND hWnd;                     /**< Handle to dumme window */
};

/**
 * Creates an instance for a keyboard
 *
 * The specified KeyboardInput_HandleData stucture should have the DirectInput and 
 * Windows handle members set (but \e not the device part). On successful
 * return, the device part in KeyboardInput_HandleData should be set.
 *
 * @param[in,out] p Pointer to a KeyboardInput_HandleData structure 
 * @return Returns true on succes, false on fail.
 */
static bool createKeyboard( KeyboardInput_HandleData *p )
{
	// check input
	assert( p != NULL && "A KeyboardInput_HandleData structure must be specified" );
	assert( p->di != NULL && "The KeyboardInput_HandleData structure must have a pointer to a DirectInput" );
	assert( p->hWnd != NULL && "The KeyboardInput_HandleData structure must have a handle to a window" );
	assert( p->device == NULL && "The KeyboardInput_HandleData should NOT have a handle to a device (yet)" );

	//
	// Create device
	//
	if(FAILED( p->di->CreateDevice( GUID_SysKeyboard, &p->device, NULL)) || p->device == NULL ) return false;
	assert( p->device != NULL && "There shoud be a device instanciated by now");

	//
	// Setup device
	//   - setup data format
	//   - setup cooperative level (non-exclucive and background)
	//   - aqurire device.
	//
	// If anything failes release the joystick and then continue with the enumeration
	//
	if( FAILED(p->device->SetDataFormat(&c_dfDIKeyboard)) ||
		FAILED(p->device->SetCooperativeLevel( p->hWnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND)) )
	{
		p->device->Unacquire();
		p->device->Release();
		p->device = NULL;
		return false;
	}
	p->device->Acquire(); // todo: move up into if statement and check the return?

	// If here, then success.
	return true;
}


/**
 * Detroys an input device
 *
 * This function frees all the resources aquired by a previous call the the create() function.
 *
 * @param _handle Handle to the input device.
 * @return Returns zero on success, non-zero on fail.
 */
__declspec(dllexport) 
uint32_t destroy(uint64_t _handle)
{
	KeyboardInput_HandleData *p = reinterpret_cast<KeyboardInput_HandleData *>(_handle);
	if( p )
	{
		// Free the device resources
		if( p->device ) {
			p->device->Unacquire();
			p->device->Release(); 
		}
		// Release DirectInput
		if( p->di ) p->di->Release();

		// destroy the window
		if( p->hWnd ) DestroyWindow(p->hWnd);

		// free the memory that held the EskyInput_HandleData structure;
		delete p;
	}
	
	// Always return success.
    return 0;
}



/**
 * Create an input device
 *
 * @return Handle to the created device, zero on fail.
 */
__declspec(dllexport) 
uint64_t create()
{
	// Create KeyboardInput_HandleData stucture.
	// Return fail if this doen't succed.
	// Set structure to all zeros.
	KeyboardInput_HandleData *p = new KeyboardInput_HandleData;
	if( p == NULL ) return 0;
	memset(p,0,sizeof *p);

	// Get handle to current module (used later).
	HINSTANCE hInstance = GetModuleHandle(NULL);

	//
	// Create a dummie window, p->hWnd. Abort if fail.
	//

	// Register dummie window class
	WNDCLASS wndclass = {0, DefWindowProc, 0,0, hInstance, NULL, NULL, NULL, NULL, _T("KeyboardInputWindowClass")};
	// todo: check if class already exists, and only register if it doen't?
	RegisterClass( &wndclass ); // Don't check the result since it will fail on all but first call here.

	// Instantiate a window. Abort on fail.
	p->hWnd = CreateWindow( _T("KeyboardInputWindowClass"), _T("KeyboardInputWindowInstance"), 0, 
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
	if( p->hWnd == NULL ) return 0;
	
	//
	// Create DirectInput8 (p->di), Abort if fail.
	//
	if( FAILED( DirectInput8Create( hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&(p->di), NULL)) ) {
		goto return_fail; // Beautiful goto ;-)
	}

	//
	// Create device (p->device). Abort if fail.
	//
	if( createKeyboard(p) == false ) goto return_fail;
	
	// if here, then success.
	// return pointer to KeyboardInput_HandleData instance as handle
	return reinterpret_cast<uint64_t>(p);

return_fail:
	// If here, then something went wrong.
	// Do cleanup, and retun 0;
	// note: destroy deletes p for us.
	destroy(reinterpret_cast<uint64_t>(p));
	return 0;
} 

/**
 * Retrun the number of channels of the device.
 *
 * Simulate 4 channels, so this function will always return 4.
 *
 * @param[in] _handle Handle to the input device.
 * @return Returns the number of channels the device has.
 */
__declspec(dllexport) 
uint32_t get_number_of_channels(uint64_t _handle)
{
	// We're simulate 4 channels, so this function will always return 4.
	return 4;
}

// Local helper macro
// (Make invisible to doxygen)
#ifndef DOXYGEN_SHOULD_SKIP_THIS
#define KEYDOWN( _KEY ) (keyboardState[_KEY]&0x80)
#endif // DOXYGEN_SHOULD_SKIP_THIS


/**
 * Capture and retrive channel data
 *
 * @param[in] _handle Handle to the input device to capture from
 * @param[out] _target Pointer to array of float to receive the data
 * @param[in] _start Index of first channel to capture
 * @param[in] _length Number of channels to receive
 * @return 0 on success, otherwise non-zero
 */
__declspec(dllexport) 
uint32_t capture(uint64_t _handle, float *_target, uint32_t _start, uint32_t _length)
{
	// Get pointer to handle data.
	KeyboardInput_HandleData *p = reinterpret_cast<KeyboardInput_HandleData *>(_handle);

	// 
	// Check input. 
	// A EskyInput_HandleData structure must be specified, and this structure must contain
	// a pointer to a DirectInput and a joystick
	//
	// If this is not the case, do nothing and retrun fail.
	//
	if( p == NULL ) return 1;
	if( p->di == NULL ) return 1;
	if(  p->device==NULL ) return 1;

	//
	// Get key states
	//
	char keyboardState[256];
	HRESULT hr;
	hr = p->device->GetDeviceState( sizeof keyboardState, &keyboardState );
	if ( FAILED(hr)) { return 1; }
	
	//
	// Fill in the result array, then return success.
	//
	size_t index = 0;
	for( uint32_t i =_start; i<(_start+_length); ++i, ++index ) {
		if( i==0 ) {
			_target[index] = (KEYDOWN(DIK_RIGHT)?1.f:0.f)-(KEYDOWN(DIK_LEFT)?1.f:0.f);
		}
		else if( i==1 ) {
			_target[index] = (KEYDOWN(DIK_UP)?1.f:0.f)-(KEYDOWN(DIK_DOWN)?1.f:0.f);
		}
		else if( i==2 ) {
			_target[index] = (KEYDOWN(DIK_W)?1.f:0.f)-(KEYDOWN(DIK_S)?1.f:0.f);
		}
		else if( i==3 ) {
			_target[index] = (KEYDOWN(DIK_D)?1.f:0.f)-(KEYDOWN(DIK_A)?1.f:0.f);
		}
		else _target[index] = 0.f;
	}
    return 0;
}

} // extern "C"





